package org.swrlapi.factory;

import org.checkerframework.checker.nullness.qual.NonNull;
import org.semanticweb.owlapi.model.AddAxiom;
import org.semanticweb.owlapi.model.IRI;
import org.semanticweb.owlapi.model.OWLAxiom;
import org.semanticweb.owlapi.model.OWLOntology;
import org.semanticweb.owlapi.model.OWLOntologyChange;
import org.semanticweb.owlapi.model.OWLOntologyManager;
import org.semanticweb.owlapi.reasoner.OWLReasoner;
import org.swrlapi.bridge.SWRLRuleEngineBridgeController;
import org.swrlapi.bridge.TargetSWRLRuleEngine;
import org.swrlapi.builtins.SWRLBuiltInBridgeController;
import org.swrlapi.core.SWRLAPIOWLOntology;
import org.swrlapi.core.SWRLAPIRule;
import org.swrlapi.core.SWRLRuleEngine;
import org.swrlapi.core.SWRLRuleRenderer;
import org.swrlapi.exceptions.SWRLAPIException;
import org.swrlapi.exceptions.SWRLBuiltInException;
import org.swrlapi.exceptions.SWRLRuleEngineException;
import org.swrlapi.exceptions.SWRLRuleException;
import org.swrlapi.exceptions.TargetSWRLRuleEngineException;
import org.swrlapi.owl2rl.OWL2RLEngine;
import org.swrlapi.parser.SWRLParseException;
import org.swrlapi.parser.SWRLParser;
import org.swrlapi.sqwrl.SQWRLQuery;
import org.swrlapi.sqwrl.SQWRLQueryEngine;
import org.swrlapi.sqwrl.SQWRLQueryRenderer;
import org.swrlapi.sqwrl.SQWRLResult;
import org.swrlapi.sqwrl.exceptions.SQWRLException;
import org.swrlapi.ui.model.SWRLAutoCompleter;

import javax.swing.*;
import java.io.File;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * This class provides an implementation of some of the core functionality required by a SWRL rule engine.
 */
class DefaultSWRLRuleAndQueryEngine implements SWRLRuleEngine, SQWRLQueryEngine
{
  @NonNull private final SWRLAPIOWLOntology swrlapiOWLOntology;
  @NonNull private final TargetSWRLRuleEngine targetSWRLRuleEngine;
  @NonNull private final SWRLBuiltInBridgeController builtInBridgeController;
  @NonNull private final SWRLRuleEngineBridgeController ruleEngineBridgeController;

  public DefaultSWRLRuleAndQueryEngine(@NonNull SWRLAPIOWLOntology swrlapiOWLOntology,
    @NonNull TargetSWRLRuleEngine targetSWRLRuleEngine,
    @NonNull SWRLRuleEngineBridgeController ruleEngineBridgeController,
    @NonNull SWRLBuiltInBridgeController builtInBridgeController) throws SWRLRuleEngineException
  {
    this.swrlapiOWLOntology = swrlapiOWLOntology;
    this.targetSWRLRuleEngine = targetSWRLRuleEngine;
    this.builtInBridgeController = builtInBridgeController;
    this.ruleEngineBridgeController = ruleEngineBridgeController;
  }

  @Override public void importAssertedOWLAxioms() throws SWRLRuleEngineException
  {
    reset();

    try {
      exportOWLAxioms2TargetRuleEngine(this.swrlapiOWLOntology.getOWLAxioms()); // OWL axioms include SWRL rules
    } catch (SWRLAPIException e) {
      throw new SWRLRuleEngineException(
        "error exporting knowledge to rule engine: " + (e.getMessage() != null ? e.getMessage() : ""), e);
    }
  }

  @Override public void importSQWRLQueryAndOWLAxioms(@NonNull String queryName)
    throws SWRLRuleEngineException, SWRLBuiltInException
  {
    reset();

    try {
      exportOWLAxioms2TargetRuleEngine(this.swrlapiOWLOntology.getOWLAxioms()); // OWL axioms include SWRL rules
      exportSQWRLQuery2TargetRuleEngine(queryName);
    } catch (SWRLAPIException e) {
      throw new SWRLRuleEngineException(
        "error exporting SQWRL query to rule engine: " + (e.getMessage() != null ? e.getMessage() : ""), e);
    }
  }

  @Override public void run() throws SWRLRuleEngineException
  {
    getTargetSWRLRuleEngine().runRuleEngine();
  }

  @Override public void exportInferredOWLAxioms() throws SWRLRuleEngineException
  {
    try {
      getSWRLAPIOWLOntology().startEventFreezeMode(); // Suspend possible event generation for bulk updates.

      // Write OWL axioms generated by built-ins in rules.
      writeOWLAxioms2OWLOntology(this.builtInBridgeController.getInjectedOWLAxioms());
      // Write OWL axioms inferred by rules.
      writeOWLAxioms2OWLOntology(this.ruleEngineBridgeController.getInferredOWLAxioms());
    } finally {
      getSWRLAPIOWLOntology().finishEventFreezeMode();
    }
  }

  @Override public void infer() throws SWRLRuleEngineException
  {
    importAssertedOWLAxioms(); // Import will call reset()
    run();
    exportInferredOWLAxioms();
  }

  @NonNull @Override public SQWRLResult runSQWRLQuery(@NonNull String queryName) throws SQWRLException
  {
    try {
      importSQWRLQueryAndOWLAxioms(queryName);
      run();
      return getSQWRLResult(queryName);
    } catch (SWRLAPIException | SWRLBuiltInException e) {
      throw new SQWRLException("error running SQWRL queries: " + (e.getMessage() != null ? e.getMessage() : ""), e);
    }
  }

  @Override public void runSQWRLQueries() throws SQWRLException
  {
    try {
      importAssertedOWLAxioms();
      exportSQWRLQueries2TargetRuleEngine();
    } catch (SWRLRuleEngineException | TargetSWRLRuleEngineException | SWRLBuiltInException e) {
      throw new SQWRLException("error processing SQWRL queries: " + (e.getMessage() != null ? e.getMessage() : ""), e);
    }

    try {
      run();
    } catch (SWRLAPIException e) {
      throw new SQWRLException("error running SQWRL queries: " + (e.getMessage() != null ? e.getMessage() : ""), e);
    }
  }

  @Override public SQWRLQuery createSQWRLQuery(@NonNull String queryName, @NonNull String queryText)
    throws SWRLParseException, SQWRLException
  {
    try {
      return this.swrlapiOWLOntology.createSQWRLQuery(queryName, queryText);
    } catch (RuntimeException | SWRLBuiltInException e) {
      throw new SQWRLException("error creating SQWRL query: " + (e.getMessage() != null ? e.getMessage() : ""), e);
    }
  }

  @NonNull @Override public SQWRLResult runSQWRLQuery(@NonNull String queryName, @NonNull String queryText)
    throws SWRLParseException, SQWRLException
  {
    createSQWRLQuery(queryName, queryText);

    return runSQWRLQuery(queryName);
  }

  @NonNull @Override public SQWRLResult getSQWRLResult(@NonNull String queryName) throws SQWRLException
  {
    return this.swrlapiOWLOntology.getSQWRLResult(queryName);
  }

  @NonNull @Override public Set<@NonNull SQWRLQuery> getSQWRLQueries()
  {
    return this.swrlapiOWLOntology.getSQWRLQueries();
  }

  @NonNull @Override public Set<@NonNull String> getSQWRLQueryNames()
  {
    return this.swrlapiOWLOntology.getSQWRLQueryNames();
  }

  @NonNull @Override public SQWRLQueryRenderer createSQWRLQueryRenderer()
  {
    return this.swrlapiOWLOntology.createSQWRLQueryRenderer();
  }

  @NonNull public OWLOntology getOWLOntology()
  {
    return getSWRLAPIOWLOntology().getOWLOntology();
  }

  @NonNull @Override public OWL2RLEngine getOWL2RLEngine()
  {
    return this.targetSWRLRuleEngine.getOWL2RLEngine();
  }

  // Convenience methods to display bridge activity

  @Override public int getNumberOfImportedSWRLRules()
  {
    return this.swrlapiOWLOntology.getNumberOfSWRLRules();
  }

  @Override public int getNumberOfAssertedOWLClassDeclarationAxioms()
  {
    return this.swrlapiOWLOntology.getNumberOfOWLClassDeclarationAxioms();
  }

  @Override public int getNumberOfAssertedOWLIndividualDeclarationsAxioms()
  {
    return this.swrlapiOWLOntology.getNumberOfOWLIndividualDeclarationAxioms();
  }

  @Override public int getNumberOfAssertedOWLObjectPropertyDeclarationAxioms()
  {
    return this.swrlapiOWLOntology.getNumberOfOWLObjectPropertyDeclarationAxioms();
  }

  @Override public int getNumberOfAssertedOWLDataPropertyDeclarationAxioms()
  {
    return this.swrlapiOWLOntology.getNumberOfOWLDataPropertyDeclarationAxioms();
  }

  @Override public int getNumberOfAssertedOWLAxioms()
  {
    return this.swrlapiOWLOntology.getNumberOfOWLAxioms();
  }

  @Override public int getNumberOfInferredOWLAxioms()
  {
    return this.ruleEngineBridgeController.getNumberOfInferredOWLAxioms();
  }

  @Override public int getNumberOfInjectedOWLAxioms()
  {
    return this.builtInBridgeController.getNumberOfInjectedOWLAxioms();
  }

  public boolean isInjectedOWLAxiom(OWLAxiom axiom)
  {
    return this.builtInBridgeController.isInjectedOWLAxiom(axiom);
  }

  // Convenience methods to display the contents of the bridge

  @NonNull @Override public Set<@NonNull SWRLAPIRule> getSWRLRules()
  {
    return this.swrlapiOWLOntology.getSWRLRules();
  }

  @Override public Optional<@NonNull SWRLAPIRule> getSWRLRule(@NonNull String ruleName) throws SWRLRuleException
  {
    return this.swrlapiOWLOntology.getSWRLRule(ruleName);
  }

  @NonNull @Override public SWRLAPIRule createSWRLRule(@NonNull String ruleName, @NonNull String rule)
    throws SWRLParseException, SWRLBuiltInException
  {
    return this.swrlapiOWLOntology.createSWRLRule(ruleName, rule);
  }

  @NonNull @Override public SWRLAPIRule createSWRLRule(@NonNull String ruleName, @NonNull String rule,
    @NonNull String comment, boolean isActive) throws SWRLParseException, SWRLBuiltInException
  {
    return this.swrlapiOWLOntology.createSWRLRule(ruleName, rule, comment, isActive);
  }

  @Override public void replaceSWRLRule(@NonNull String originalRuleName, @NonNull String ruleName,
    @NonNull String rule, @NonNull String comment, boolean isActive) throws SWRLParseException, SWRLBuiltInException
  {
    this.swrlapiOWLOntology.replaceSWRLRule(originalRuleName, ruleName, rule, comment, isActive);
  }

  @Override public void deleteSWRLRule(@NonNull String ruleName)
  {
    this.swrlapiOWLOntology.deleteSWRLRule(ruleName);
  }

  @Override public boolean isSWRLBuiltInIRI(@NonNull IRI iri)
  {
    return this.swrlapiOWLOntology.isSWRLBuiltInIRI(iri);
  }

  @Override public boolean isSWRLBuiltIn(@NonNull String shortName)
  {
    return this.swrlapiOWLOntology.isSWRLBuiltIn(shortName);
  }

  @NonNull @Override public Set<@NonNull IRI> getSWRLBuiltInIRIs()
  {
    return Collections.unmodifiableSet(this.swrlapiOWLOntology.getSWRLBuiltInIRIs());
  }

  @NonNull @Override public SWRLParser createSWRLParser()
  {
    return this.swrlapiOWLOntology.createSWRLParser();
  }

  @NonNull @Override public SWRLAutoCompleter createSWRLAutoCompleter()
  {
    return this.swrlapiOWLOntology.createSWRLAutoCompleter();
  }

  @NonNull @Override public SWRLRuleRenderer createSWRLRuleRenderer()
  {
    return this.swrlapiOWLOntology.createSWRLRuleRenderer();
  }

  @NonNull @Override public Set<@NonNull OWLAxiom> getAssertedOWLAxioms()
  {
    return this.swrlapiOWLOntology.getOWLAxioms();
  }

  @NonNull @Override public Set<@NonNull OWLAxiom> getInferredOWLAxioms()
  {
    return this.ruleEngineBridgeController.getInferredOWLAxioms();
  }

  @NonNull @Override public Set<@NonNull OWLAxiom> getInjectedOWLAxioms()
  {
    return this.builtInBridgeController.getInjectedOWLAxioms();
  }

  @NonNull @Override public String getRuleEngineName()
  {
    return this.targetSWRLRuleEngine.getTargetRuleEngineName();
  }

  @NonNull @Override public String getTargetQueryEngineName()
  {
    return this.targetSWRLRuleEngine.getTargetRuleEngineName();
  }

  @NonNull @Override public String getRuleEngineVersion()
  {
    return this.targetSWRLRuleEngine.getTargetRuleEngineVersion();
  }

  @NonNull @Override public String getTargetQueryEngineVersion()
  {
    return this.targetSWRLRuleEngine.getTargetRuleEngineVersion();
  }

  @NonNull @Override public OWLReasoner getOWLReasoner()
  {
    return this.targetSWRLRuleEngine.getOWLReasoner();
  }

  @Override public void loadExternalSWRLBuiltInLibraries(@NonNull File swrlBuiltInLibraryDirectory)
  {
    this.swrlapiOWLOntology.getSWRLBuiltInLibraryManager()
      .loadExternalSWRLBuiltInLibraries(swrlBuiltInLibraryDirectory);
  }

  @NonNull @Override public Icon getRuleEngineIcon()
  {
    return this.targetSWRLRuleEngine.getTargetRuleEngineIcon();
  }

  @NonNull @Override public Icon getTargetQueryEngineIcon()
  {
    return this.targetSWRLRuleEngine.getTargetRuleEngineIcon();
  }

  @NonNull @Override public SWRLRuleEngine getSWRLRuleEngine()
  {
    return this;
  }

  @NonNull @Override public SWRLAPIOWLOntology getSWRLAPIOWLOntology()
  {
    return this.swrlapiOWLOntology;
  }

  private void reset()
  {
    try {
      getTargetSWRLRuleEngine().resetRuleEngine(); // Reset the target rule engine
      getBuiltInBridgeController().reset();
      getOWL2RLEngine().resetRuleSelectionChanged();
      this.swrlapiOWLOntology.processOntology();
    } catch (SWRLBuiltInException e) {
      throw new SWRLRuleEngineException("error running rule engine: " + (e.getMessage() != null ? e.getMessage() : ""),
        e);
    }
  }

  private void exportSQWRLQuery2TargetRuleEngine(@NonNull String activeQueryName)
    throws SWRLRuleEngineException, TargetSWRLRuleEngineException, SWRLBuiltInException
  {
    for (SQWRLQuery query : this.swrlapiOWLOntology.getSQWRLQueries()) {
      query.setActive(query.getQueryName().equalsIgnoreCase(activeQueryName));
      exportSQWRLQuery2TargetRuleEngine(query);
    }
  }

  private void exportSQWRLQueries2TargetRuleEngine()
    throws SWRLRuleEngineException, TargetSWRLRuleEngineException, SWRLBuiltInException
  {
    for (SQWRLQuery query : this.swrlapiOWLOntology.getSQWRLQueries()) {
      query.setActive(true);
      exportSQWRLQuery2TargetRuleEngine(query);
    }
  }

  private void exportSQWRLQuery2TargetRuleEngine(@NonNull SQWRLQuery query)
    throws SWRLRuleEngineException, TargetSWRLRuleEngineException, SWRLBuiltInException
  {
    getTargetSWRLRuleEngine().defineSQWRLQuery(query);
  }

  @NonNull private TargetSWRLRuleEngine getTargetSWRLRuleEngine() throws SWRLRuleEngineException
  {
    if (this.targetSWRLRuleEngine == null)
      throw new SWRLRuleEngineException("no target rule engine specified");

    return this.targetSWRLRuleEngine;
  }

  private void exportOWLAxioms2TargetRuleEngine(@NonNull Set<@NonNull OWLAxiom> axioms)
    throws SWRLRuleEngineException, TargetSWRLRuleEngineException
  {
    for (OWLAxiom axiom : axioms)
      getTargetSWRLRuleEngine().defineOWLAxiom(axiom);
  }

  private void writeOWLAxioms2OWLOntology(@NonNull Set<@NonNull OWLAxiom> axioms) throws SWRLRuleEngineException
  {
    OWLInferredAxiomFilter inferredAxiomFilter = new OWLInferredAxiomFilter(axioms);
    List<OWLAxiom> filteredAxioms = axioms.stream().filter(a -> !a.accept(inferredAxiomFilter))
      .collect(Collectors.toList());
    List<? extends OWLOntologyChange> changes = filteredAxioms.stream().map(a -> new AddAxiom(getOWLOntology(), a))
      .collect(Collectors.toList());

    try {
      getOWLOntologyManager().applyChanges(changes);
    } catch (RuntimeException e) {
      throw new SWRLRuleEngineException("Error writing OWL axioms to ontology", e);
    }
  }

  @NonNull private OWLOntologyManager getOWLOntologyManager()
  {
    return this.getSWRLAPIOWLOntology().getOWLOntologyManager();
  }

  @NonNull private SWRLBuiltInBridgeController getBuiltInBridgeController()
  {
    return this.builtInBridgeController;
  }
}
